Skip to content

Fix range tombstone GC with ingest-behind resurrecting deleted keys - #15653

Open
Potato-Yao wants to merge 2 commits into
meta:mainfrom
Potato-Yao:main
Open

Potato-Yao wants to merge 2 commits into
meta:mainfrom
Potato-Yao:main

Conversation

@Potato-Yao

Copy link
Copy Markdown

Summary

Fix #15425. Compaction could garbage-collect a range tombstone at the bottommost level even when ingest-behind was enabled. Because a later ingest-behind backfill inserts files below all existing data, the dropped tombstone no longer masked the backfilled values, so range-deleted keys became visible again.

Previous behavior and root cause

In CompactionOutputs::AddRangeDels(), a range tombstone was considered obsolete and dropped when:

  • it was at the bottommost level,
  • tombstone.seq_ <= earliest_snapshot, and
  • with user-defined timestamps, its timestamp was below full_history_ts_low.

That is valid only while no older data can later appear beneath the tombstone. Ingest-behind breaks that assumption by placing files with older sequence numbers below all existing data. Point tombstones already had the analogous protection in CompactionIterator::ProcessDeletion() (!compaction_->allow_ingest_behind()), but the range-tombstone path did not.

Reproducer: Put("a", "new"), Delete("b"), DeleteRange("c", "d"), flush, force a bottommost compaction, then ingest-behind {a,b,c,c2,d,e} = "old". Before the fix, c and c2 read as "old" instead of NOT_FOUND.

Implementation

db/compaction/compaction_outputs.cc: consider_drop in CompactionOutputs::AddRangeDels() now additionally requires both immutable_options().cf_allow_ingest_behind and immutable_options().allow_ingest_behind to be false. No API, on-disk format, or MANIFEST change.

Test Plan

Added ExternalSSTFileTest.IngestBehindPreservesRangeDeletions in db/external_sst_file_test.cc. The test runs under four ingestion configurations (checksum verification and two-phase ingest each on/off) and, inside each, loops over:

  • per-CF cf_allow_ingest_behind versus DB-wide allow_ingest_behind
  • with and without a forced bottommost compaction before the ingest

It checks that a newer value survives (a), a point deletion stays deleted (b), range-deleted keys stay deleted (c, c2), the exclusive end boundary is unaffected (d), and a key outside the range is backfilled (e).

Validation performed:

  • Without the fix (guard temporarily removed): all 4 parameter variants fail only for compact_before_ingest = true, under both option styles, with Get("c") and Get("c2") returning "old". The no-compaction controls pass.
  • With the fix: 4/4 tests pass.
  • Repeat run: 400 executions (-r100) pass with no failures.

Commands:

DISABLE_WARNING_AS_ERROR=1 build_tools/rockstest.sh external_sst_file_test \
    --gtest_filter='*IngestBehindPreservesRangeDeletions*'

DISABLE_WARNING_AS_ERROR=1 build_tools/rocksptest.sh external_sst_file_test \
    -r100 --gtest_filter='*IngestBehindPreservesRangeDeletions*'

DISABLE_WARNING_AS_ERROR=1 is needed only because of pre-existing GCC 16 BlobDB warnings. Not yet run: full make check, COERCE_CONTEXT_SWITCH=1 stress mode, and non-Linux platforms.

Summary:
Add IngestBehindPreservesRangeDeletions to reproduce meta#15425: Universal
compaction drops a range tombstone before an ingest-behind backfill, so
historical values in the deleted range become visible again.

Compare ingestion with and without prior compaction using both the
column-family and legacy DB-wide ingest-behind options. Check newer
values, point deletions, range deletions, the exclusive end boundary,
and an unaffected key across the existing ingestion parameters.

This is a reproducer-only checkpoint. Production behavior is unchanged;
the new test intentionally fails until the compaction fix is added.

Test Plan:
Built external_sst_file_test in debug mode with
AUTO_CLEAN=1 DISABLE_WARNING_AS_ERROR=1 make -j6 external_sst_file_test.
The warning override was needed for existing GCC 16 BlobDB warnings.
Ran *IngestBehindPreservesRangeDeletions* and repeated it 100 times:
all four parameter variants fail only on the two range-deleted keys
after compaction. The no-compaction controls and other assertions pass.
Formatting and git diff --check pass. Full-suite and coerce-mode
validation are deferred until the fix stage.
…ed, which could cause a subsequent historical backfill to resurrect range-deleted keys.
Copilot AI balanced review requested due to automatic review settings November 11, 2026 17:24
@meta-cla meta-cla Bot added the CLA Signed label Nov 11, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

No active review issues block the change to preserve range tombstones when ingest-behind is enabled.

No open issues 🎉

What changed in this PR

Fixes #15425 by preserving range tombstones during compaction when ingest-behind is enabled. Previously, compaction could drop these tombstones, allowing a later backfill to resurrect range-deleted keys.

Changes:

  • Prevents obsolete range-tombstone removal when either per-column-family or DB-wide ingest-behind is enabled.
  • Adds regression coverage for both option styles, with and without compaction, checking deleted keys, newer values, and range boundaries.

No public API or persisted-format changes are introduced; tombstone-removal behavior remains unchanged when ingest-behind is disabled.

Validation: The PR reports all four parameter variants and 400 repeated executions passing, with the regression reproduced when the fix is removed. Full-suite, forced-context-switch, and non-Linux validation remain outstanding.

File Description
db/​external_sst_file_test.cc Adds ingest-behind regression coverage for range-deletion preservation.
db/​compaction/​compaction_outputs.cc Guards range-tombstone removal with both ingest-behind options.

Review effort: Balanced

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants